﻿using System;
using System.Text;

namespace ParserCore
{

    public interface IVisitor
    {
        void Visit(Constant exp);
        void Visit(Identifier exp);
        void Visit(Call exp);
        void Visit(SubCell exp);
        void Visit(NDimArrayBuilder exp);
        void Visit(AnonymousFunctionBuilder exp);
        void Visit(CellBuilder exp);
        void Visit(SingleExpressionStatement stmt);
        void Visit(AssignmentStatement stmt);
        void Visit(BlockStatement stmt);
        void Visit(IfThenElseStatement stmt);
        void Visit(SwitchCaseStatement stmt);
        void Visit(ForStatement stmt);
        void Visit(WhileStatement stmt);
        void Visit(ContinueStatement stmt);
        void Visit(BreakStatement stmt);
        void Visit(TryCatchStatement stmt);
        void Visit(ReturnStatement stmt);
        void Visit(FunctionDefinitionStatement stmt);
        void Visit(GlobalDeclarationStatement stmt);
        void Visit(PersistentDeclarationStatement stmt);
    }

    public interface IVisited
    {
        void Accept(IVisitor visitor);
    }

    public class ToStringVisitor : IVisitor
    {
        private StringBuilder State;

        public string GetString(IVisited visited)
        {
            State = new StringBuilder();
            visited.Accept(this);
            return State.ToString();
        }

        public void Visit(Constant exp)
		{
            State.AppendFormat("{0}",exp.Const);
		}
        
        public void Visit(Identifier exp)
		{
			State.AppendFormat("{0}",exp.ID);
		}
        
        /// <summary>
        /// Vraca ime identifikatora (operatora), a za njim listu argumenata unutar '(' i ')' odvojenu sa ','
        /// Primjer: op(arg1,arg2,arg3)
        /// </summary>
        public void Visit(Call exp)
		{
            exp.Called.Accept(this);
            State.Append("(");
            for (int i = 0; i < exp.ArgumentList.Length - 1; i++)
            {
                exp.ArgumentList[i].Accept(this);
                State.Append(",");
            }
            if (exp.ArgumentList.Length > 0)
            {
                (exp.ArgumentList[exp.ArgumentList.Length - 1]).Accept(this);
            }
            State.Append(")");
		}

        public void Visit(SubCell exp)
        {
            exp.CellIdentifier.Accept(this);
            State.Append("{");
            for (int i = 0; i < exp.ArgumentList.Length - 1; i++)
            {
                exp.ArgumentList[i].Accept(this);
                State.Append(",");
            }
            if (exp.ArgumentList.Length > 0)
            {
                (exp.ArgumentList[exp.ArgumentList.Length - 1]).Accept(this);
            }
            State.Append("}");
        }

        /// <summary>
        /// Vraca rezultat u unutar '[' i ']' . Vrste su odvojene sa ';', pojedinacni elementi sa ','.
        /// Primjer: [a11,a12;a21,a22]
        /// </summary>
        public void Visit(NDimArrayBuilder exp)
		{
            State.Append("[");
            for (int i = 0; i < exp.Exprs.Length - 1; ++i)
            {
                for (int j = 0; j < exp.Exprs[i].Length - 1; ++j)
                {
                    exp.Exprs[i][j].Accept(this);
                    State.Append(",");
                }
                if (exp.Exprs[i].Length > 0)
                {
                    (exp.Exprs[i][exp.Exprs[i].Length - 1]).Accept(this);
                }
                State.Append(";");
            }
            if (exp.Exprs.Length > 0)
            {
                int i = exp.Exprs.Length - 1;
                for (int j = 0; j < exp.Exprs[i].Length - 1; ++j)
                {
                    exp.Exprs[i][j].Accept(this);
                    State.Append(",");
                }
                if (exp.Exprs[i].Length > 0)
                {
                    (exp.Exprs[i][exp.Exprs[i].Length - 1]).Accept(this);
                }
            }
            State.Append("]");
		}

        /// <summary>
        /// Vraca '@' , zatim listu argumenata unutar '(', ')' odvojenu sa ','. Zatim slijedi izraz.
        /// Primjer: @(x,y)+(x,y)
        /// </summary>
        public void Visit(AnonymousFunctionBuilder exp)
		{
            State.Append("@(");
            for (int i = 0; i < exp.Arguments.Length - 1; ++i)
            {
                exp.Arguments[i].Accept(this);
                State.Append(",");
            }
            if (exp.Arguments.Length > 0)
            {
                exp.Arguments[exp.Arguments.Length - 1].Accept(this);
            }
            State.Append(")");
            exp.Body.Accept(this);
		}

        /// <summary>
        /// Vraca rezultat u unutar '{' i '}' . Vrste su odvojene sa ';', pojedinacni elementi sa ','.
        /// Primjer: {a11,a12;a21,a22}
        /// </summary>
        public void Visit(CellBuilder exp)
		{
            State.Append("{");
            for (int i = 0; i < exp.Exprs.Length - 1; ++i)
            {
                for (int j = 0; j < exp.Exprs[i].Length - 1; ++j)
                {
                    exp.Exprs[i][j].Accept(this);
                    State.Append(",");
                }
                if (exp.Exprs[i].Length > 0)
                {
                    (exp.Exprs[i][exp.Exprs[i].Length - 1]).Accept(this);
                }
                State.Append(";");
            }
            if (exp.Exprs.Length > 0)
            {
                int i = exp.Exprs.Length - 1;
                for (int j = 0; j < exp.Exprs[i].Length - 1; ++j)
                {
                    exp.Exprs[i][j].Accept(this);
                    State.Append(",");
                }
                if (exp.Exprs[i].Length > 0)
                {
                    (exp.Exprs[i][exp.Exprs[i].Length - 1]).Accept(this);
                }
            }
            State.Append("}");
		}

        /// <summary>
        /// Vraca izraz
        /// </summary>
        public void Visit(SingleExpressionStatement stmt)
		{
            State.Append(stmt.Expr.ToString());
		}

        /// <summary>
        /// Vraca listu izraza sa lijeve strane odvojenu sa ',' unutar '[' i ']', 
        /// a zatim slijedi '=' pa izraz sa desne strane. Primjeri:
        /// [a]=+(x,7)
        /// [q,r]=qr(a)
        /// </summary>
        public void Visit(AssignmentStatement stmt)
		{

            State.Append("[");
            for (int i = 0; i < stmt.LeftHandSide.Length - 1; ++i)
            {
                stmt.LeftHandSide[i].Accept(this);
                State.Append(",");
            }
            if (stmt.LeftHandSide.Length > 0)
            {
                stmt.LeftHandSide[stmt.LeftHandSide.Length - 1].Accept(this);
            }


            State.AppendFormat("]=");
            stmt.RightHandSide.Accept(this);
        }

        /// <summary>
        /// Vraca listu naredbi odvojenu sa ';'. Primjer:
        /// [x]=+(2,3);y;566
        /// </summary>
        public void Visit(BlockStatement stmt)
        {
            for (int i = 0; i < stmt.StatementList.Length - 1; ++i)
            {
                stmt.StatementList[i].Accept(this);
                State.Append(";");
            }
            if (stmt.StatementList.Length > 0)
            {
                stmt.StatementList[stmt.StatementList.Length - 1].Accept(this);
            }
        }

        /// <summary>
        /// Vraca rezultat u obliku if(uslovni_izraz){naredba}else{naredba}. Primjer:
        /// if(==(x,3)){[a]=4;[b]=5}else{}
        /// </summary>
        public void Visit(IfThenElseStatement stmt)
		{
            State.Append("if(");
            stmt.Condition.Accept(this);
            State.Append("){");
            stmt.IfConditionTrueStatement.Accept(this);
            State.Append("}else{");
            stmt.IfConditionFalseStatement.Accept(this);
            State.Append("}");
        }

        /// <summary>
        /// Vraca rezultat u obliku "switch(izraz)case(izraz){naredba}...otherwise{naredba}". Primjer:
        /// switch(x)case(+(1,2)){[n]=0}case(4){[n]=1}otherwise{[n]=-1}
        /// </summary>
        public void Visit(SwitchCaseStatement stmt)
        {
            State.Append("switch(");
            stmt.Expr.Accept(this);
            State.Append(")");

            for (int i = 0; i < stmt.Cases.Length; ++i)
            {
                State.Append("case(");
                stmt.Cases[i].Item1.Accept(this);
                State.Append("){");
                stmt.Cases[i].Item2.Accept(this);
                State.Append("}");
            }
            State.Append("otherwise{");
            stmt.Otherwise.Accept(this);
            State.Append("}");
        }

        /// <summary>
        /// Vraca rezultat u obliku "for(id=izraz){naredba}". Primjer:
        /// for(k=A){[x]=+(x,k)}
        /// </summary>
        public void Visit(ForStatement stmt)
		{
            State.Append("for(");
            stmt.ID.Accept(this);
            State.Append("=");
            stmt.Expr.Accept(this);
            State.Append("){");
            stmt.LoopStatement.Accept(this);
            State.Append("}");
		}

        /// <summary>
        /// Vraca rezultat u obliku "while(uslovni_izraz){naredba}" Primjer:
        /// while(>(x,0)){[x]=-(x,1)}
        /// </summary>
        public void Visit(WhileStatement stmt)
		{
            State.Append("while(");
            stmt.Condition.Accept(this);
            State.Append("){");
            stmt.LoopStatement.Accept(this);
            State.Append("}");
		}

        /// <summary>
        /// vraca "continue"
        /// </summary>
        public void Visit(ContinueStatement stmt)
		{
            State.Append("continue");
		}

        /// <summary>
        /// vraca "break"
        /// </summary>
        public void Visit(BreakStatement stmt)
		{
            State.Append("break");
		}

        /// <summary>
        /// Vraca rezultat u obliku "try{naredba}catch(id){naredba}". Primjer:
        /// try{fun(5)}catch(e){e}
        /// </summary>
        public void Visit(TryCatchStatement stmt)
		{
            State.Append("try{");
            stmt.TryStatement.Accept(this);
            State.Append("}catch(");
            stmt.CaughtID.Accept(this);
            State.Append("){");
            stmt.CatchStatement.Accept(this);
            State.Append("}");
		}

        /// <summary>
        /// vraca "return"
        /// </summary>
        public void Visit(ReturnStatement stmt)
		{
            State.Append("return");
		}

        /// <summary>
        /// Vraca rezultat u obliku "function[lista_povratnih_vrijednosti]=ime_funkcije(lista_parametara){tijelo_funkcije}"
        /// lista_povratnih_vrijednosti i lista_parametara su identifikatori odvojeni sa ','
        /// tijelo_funkcije je naredba. Primjer:
        /// function[a,b]=fun(c){[a]=c;[b]=*(a,c);return}
        /// </summary>
        public void Visit(FunctionDefinitionStatement stmt)
		{
            State.Append("function[");

            for (int i = 0; i < stmt.ReturnList.Length - 1; ++i)
            {
                stmt.ReturnList[i].Accept(this);
                State.Append(",");
            }
            if (stmt.ReturnList.Length > 0)
            {
                stmt.ReturnList[stmt.ReturnList.Length - 1].Accept(this);
            }

            State.Append("]=");
            stmt.FcnName.Accept(this);
            State.Append("(");

            for (int i = 0; i < stmt.ParameterList.Length - 1; ++i)
            {
                stmt.ParameterList[i].Accept(this);
                State.Append(",");
            }
            if (stmt.ParameterList.Length > 0)
            {
                stmt.ParameterList[stmt.ParameterList.Length - 1].Accept(this);
            }
            State.Append("){");
            stmt.FunctionBody.Accept(this);
            State.Append("}");
		}

        /// <summary>
        /// vraca rezultat u obliku "global(lista_identifikatora)"
        /// lista_identifikatora su identifikatori odvojeni sa ','. Primjer:
        /// global(x,y)
        /// </summary>
        public void Visit(GlobalDeclarationStatement stmt)
		{
            State.Append("global(");

            for (int i = 0; i < stmt.DeclArray.Length - 1; ++i)
            {
                stmt.DeclArray[i].Accept(this);
                State.Append(",");
            }
            if (stmt.DeclArray.Length > 0)
            {
               stmt.DeclArray[stmt.DeclArray.Length - 1].Accept(this);
            }

            State.Append(")");
		}

        /// <summary>
        /// vraca rezultat u obliku "persistent(lista_identifikatora)"
        /// lista_identifikatora su identifikatori odvojeni sa ','. Primjer:
        /// persistent(x,y)
        /// </summary>
        public void Visit(PersistentDeclarationStatement stmt)
		{
            State.Append("persistent(");

            for (int i = 0; i < stmt.DeclArray.Length - 1; ++i)
            {
                stmt.DeclArray[i].Accept(this);
                State.Append(",");
            }
            if (stmt.DeclArray.Length > 0)
            {
               stmt.DeclArray[stmt.DeclArray.Length - 1].Accept(this);
            }

            State.Append(")");
		}
    }

    public class EqualsVisitor : IVisitor
    {
        private bool State;
        private object Object;

        private bool IsSameType(IVisited visited)
        {
			State &= (Object != null && visited.GetType() == Object.GetType());
			if (!State)	
				return false;
			else
				return true;
        }

        public bool IsEqual(IVisited visited, object obj)
        {
            State = true;
            Object = obj;
            visited.Accept(this);
            return State;
        }

        public void Visit(Constant exp)
        {   
            if (!IsSameType(exp)) return;
            Constant obj = (Constant)Object;

            State &= (exp.Const.Equals(obj.Const)
				&& exp.ConstType.Equals(obj.ConstType));
        }

        public void Visit(Identifier exp)
        {
            if (!IsSameType(exp)) return;
            Identifier obj = (Identifier)Object;

            State &= exp.ID.Equals(obj.ID);
        }

        public void Visit(Call exp)
		{
            if (!IsSameType(exp)) return;
            Call obj = (Call)Object;

            Object = obj.Called;
            exp.Called.Accept(this);

            State &= (exp.ArgumentList.Length == obj.ArgumentList.Length);
            if (!State) return;

            for (int i = 0; i < exp.ArgumentList.Length; ++i)
            {
				Object = obj.ArgumentList[i];
				exp.ArgumentList[i].Accept(this);
				if (!State) return;
            }
		}

        public void Visit(SubCell exp)
        {
            if (!IsSameType(exp)) return;
            SubCell obj = (SubCell)Object;

            Object = obj.CellIdentifier;
            exp.CellIdentifier.Accept(this);

            State &= (exp.ArgumentList.Length == obj.ArgumentList.Length);
            if (!State) return;

            for (int i = 0; i < exp.ArgumentList.Length; ++i)
            {
                Object = obj.ArgumentList[i];
                exp.ArgumentList[i].Accept(this);
                if (!State) return;
            }
        }

		public void Visit(NDimArrayBuilder exp)
		{
			if (!IsSameType(exp)) return;
			NDimArrayBuilder obj = (NDimArrayBuilder)Object;

			State &= (exp.Exprs.Length == obj.Exprs.Length);
			if (!State) return;

			for (int i = 0; i < exp.Exprs.Length; ++i)
			{
				State &= (exp.Exprs[i].Length == obj.Exprs[i].Length);
				if (!State) return;

				for (int j = 0; j < exp.Exprs[i].Length; ++j)
				{
					Object = obj.Exprs[i][j];
					exp.Exprs[i][j].Accept(this);
					if (!State) return;
				}
			}
		}

        public void Visit(AnonymousFunctionBuilder exp)
		{
			if (!IsSameType(exp)) return;
			AnonymousFunctionBuilder obj = (AnonymousFunctionBuilder)Object;

			Object = obj.Body;
			exp.Body.Accept(this);
			if (!State) return;

			State &= (obj.Arguments.Length == exp.Arguments.Length);
			if (!State) return;

			for (int i = 0; i < exp.Arguments.Length; ++i)
			{
				Object = obj.Arguments[i];
				exp.Arguments[i].Accept(this);
				if (!State) return;
			}


		}

        public void Visit(CellBuilder exp)
		{
			if (!IsSameType(exp)) return;
			CellBuilder obj = (CellBuilder)Object;

			State &= (exp.Exprs.Length == obj.Exprs.Length);
			if (!State) return;

			for (int i = 0; i < exp.Exprs.Length; ++i)
			{
				State &= (exp.Exprs[i].Length == obj.Exprs[i].Length);
				if (!State) return;

				for (int j = 0; j < exp.Exprs[i].Length; ++j)
				{
					Object = obj.Exprs[i][j];
					exp.Exprs[i][j].Accept(this);
					if (!State) return;
				}
			}

		}

        public void Visit(SingleExpressionStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			SingleExpressionStatement obj = (SingleExpressionStatement)Object;

			Object = obj.Expr;
			stmt.Expr.Accept(this);
		}

        public void Visit(AssignmentStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			AssignmentStatement obj = (AssignmentStatement)Object;

			Object = obj.RightHandSide;
			stmt.RightHandSide.Accept(this);
			if (!State) return;

			State &= (obj.LeftHandSide.Length == stmt.LeftHandSide.Length);
			if (!State) return;

			for (int i = 0; i < stmt.LeftHandSide.Length; i++)
			{
				Object = obj.LeftHandSide[i];
				stmt.LeftHandSide[i].Accept(this);
				if (!State) return;
			}
		}

        public void Visit(BlockStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			BlockStatement obj = (BlockStatement)Object;

			State &= (obj.StatementList.Length == stmt.StatementList.Length);
			if (!State) return;

			for (int i = 0; i < stmt.StatementList.Length; ++i)
			{
				Object = obj.StatementList[i];
				stmt.StatementList[i].Accept(this);
			}
		}

		public void Visit(IfThenElseStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			IfThenElseStatement obj = (IfThenElseStatement)Object;

			Object = obj.Condition;
			stmt.Condition.Accept(this);
			if (!State) return;
			
			Object = obj.IfConditionTrueStatement;
			stmt.IfConditionTrueStatement.Accept(this);
			if (!State) return;
			
			Object = obj.IfConditionFalseStatement;
			stmt.IfConditionFalseStatement.Accept(this);
			if (!State) return;
		}

        public void Visit(SwitchCaseStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			SwitchCaseStatement obj = (SwitchCaseStatement)Object;

			State &= (stmt.Cases.Length == obj.Cases.Length);
			if (!State) return;

			Object = obj.Expr;
			stmt.Expr.Accept(this);
			if (!State) return;

			Object = obj.Otherwise;
			stmt.Otherwise.Accept(this);
			if (!State) return;

			for (int i = 0; i < stmt.Cases.Length; ++i)
			{
				Object = obj.Cases[i].Item1;
				stmt.Cases[i].Item1.Accept(this);
				if (!State) return;

				Object = obj.Cases[i].Item2;
				stmt.Cases[i].Item2.Accept(this);
				if (!State) return;
			}
		}

        public void Visit(ForStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			ForStatement obj = (ForStatement)Object;

			Object = obj.ID;
			stmt.ID.Accept(this);
			if (!State) return;

			Object = obj.Expr;
			stmt.Expr.Accept(this);
			if (!State) return;

			Object = obj.LoopStatement;
			stmt.LoopStatement.Accept(this);
			if (!State) return;
		}

        public void Visit(WhileStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			WhileStatement obj = (WhileStatement)Object;

			Object = obj.Condition;
			stmt.Condition.Accept(this);
			if (!State) return;

			Object = obj.LoopStatement;
			stmt.LoopStatement.Accept(this);
			if (!State) return;
		}

        public void Visit(ContinueStatement stmt)
		{
			if (!IsSameType(stmt)) return;
		}

        public void Visit(BreakStatement stmt)
		{
			if (!IsSameType(stmt)) return;
		}

        public void Visit(TryCatchStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			TryCatchStatement obj = (TryCatchStatement)Object;

			Object = obj.TryStatement;
			stmt.TryStatement.Accept(this);
			if (!State) return;

			Object = obj.CatchStatement;
			stmt.CatchStatement.Accept(this);
			if (!State) return;

			Object = obj.CaughtID;
			stmt.CaughtID.Accept(this);
			if (!State) return;
		}

        public void Visit(ReturnStatement stmt)
		{
			if (!IsSameType(stmt)) return;
		}

        public void Visit(FunctionDefinitionStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			FunctionDefinitionStatement obj = (FunctionDefinitionStatement)Object;

			State &= (stmt.ReturnList.Length == obj.ReturnList.Length);
			State &= (stmt.ParameterList.Length == obj.ParameterList.Length);
			if (!State) return;

			Object = obj.FcnName;
			stmt.FcnName.Accept(this);
			if (!State) return;

			Object = obj.FunctionBody;
			stmt.FunctionBody.Accept(this);
			if (!State) return;

			for (int i = 0; i < stmt.ReturnList.Length; ++i)
			{
				Object = obj.ReturnList[i];
				stmt.ReturnList[i].Accept(this);
				if (!State) return;
			}

			for (int i = 0; i < stmt.ParameterList.Length; ++i)
			{
				Object = obj.ParameterList[i];
				stmt.ParameterList[i].Accept(this);
				if (!State) return;
			}


		}

        public void Visit(GlobalDeclarationStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			GlobalDeclarationStatement obj = (GlobalDeclarationStatement)Object;

			State &= (obj.DeclArray.Length == stmt.DeclArray.Length);
			if (!State) return;

			for (int i = 0; i < stmt.DeclArray.Length; ++i)
			{
				Object = obj.DeclArray[i];
				stmt.DeclArray[i].Accept(this);
				if (!State) return;
			}
		}

        public void Visit(PersistentDeclarationStatement stmt)
		{
			if (!IsSameType(stmt)) return;
			PersistentDeclarationStatement obj = (PersistentDeclarationStatement)Object;

			State &= (obj.DeclArray.Length == stmt.DeclArray.Length);
			if (!State) return;

			for (int i = 0; i < stmt.DeclArray.Length; ++i)
			{
				Object = obj.DeclArray[i];
				stmt.DeclArray[i].Accept(this);
				if (!State) return;
			}
		}
    }
}